import 'dart:io';
import 'dart:isolate';
import 'dart:typed_data';

import 'package:cloudapp/bus/dtypes.dart';
import 'package:cloudapp/bus/event_bus.dart';
import 'package:cloudapp/common/common.dart';
import 'package:cloudapp/esdk/db.dart';
import 'package:cloudapp/esdk/esync.dart';
import 'package:cloudapp/esdk/model/sync_file.dart';
import 'package:cloudapp/exceptions/exceptions.dart';
import 'package:crypto/crypto.dart';
import 'package:convert/convert.dart';
import 'package:cloudapp/model/upload_list.dart';
import 'dart:convert' as convert;

Future<UploadFileResult> asyncUploadTask(UploadFileInfo info) async {
  print('run on async upload task');
  // 设置 ESync 对象
  ESync.i().setToken(info.userId, info.token);
  ESync.i().setBase(info.baseHost);
  // 计算文件哈希
  print('up file ${info.filePath}');
  final hash = await getFileHash(info.filePath);
  // 上传文件
  final fileId = await uploadFile(info, hash);

  var res = UploadResult.UploadSucceed;
  if(fileId == null) {
    res = UploadResult.UploadFailed;
  }

  final result = UploadFileResult(
    sessionId: info.sessionId,
    fileId: fileId,
    fileHash: hash,
    localFileId: info.localFileId,
    localAlbumId: info.localAlbumId,
    fileName: info.fileName,
    result: res,
  );
  return result;
}

Future<void> taskEntry(ReceivePort rIn, SendPort snd) async {
  await for (var msg in rIn) {
    final info = msg as UploadFileInfo;
    asyncUploadTask(info).then((result) {
      print('wait for next...');
      snd.send(result);
    }).catchError((error) {
      print('async upload task on error!');
    });
    print("why not running parallel???");
  }
}

/*
  执行文件上传
  1. 计算文件哈希，与文件大小一起组装唯一标识
  2. 快速上传
  3. 若快速上传失败，则全量上传
   */
Future<int> uploadFile(UploadFileInfo info, String hash) async {
  try {
    return await ESync.i().quickUpload(info.serverAlbumId, info.fileName, hash);
  } on RequestException catch (e) {
    print('error: ${e.httpCode}, ${e.msg}');
    if(e.httpCode != 404) {
      throw e;
    }
    // full upload
    // 继续向下执行，即为完整上传
  } catch(e, stack) {
    print('unknown stack: ');
    print(stack);
    throw e;
  }

  // 视频文件需要走另外的传输通道
  if(info.type == FileType.Video) {
    String thumb = "";
    if(info.videoThumb!=null && info.videoThumb.isNotEmpty) {
      thumb = convert.base64Encode(info.videoThumb);
    }
    return await ESync.i().fullVideoUpload(info.serverAlbumId, info.fileName, hash, info.filePath, thumb);
  } else if(info.type == FileType.Picture) {
    return await ESync.i().fullPictureUpload(info.serverAlbumId, info.fileName, hash, info.filePath);
  }

  return null;
}

Future<String> getFileHash(String filePath) async {
  final file = File(filePath);
  final size = await file.length();
  print('file size is $size');

  if(size > 20 * 1024 * 1024) {
    return await getBigFileHash(file, size);
  }

  final bdata = ByteData(4);
  bdata.setUint32(0, size);

  final hashBytes = List<int>();
  final digest = sha1.convert(await file.readAsBytes());

  for(var i=0; i!= 20; ++i) {
    hashBytes.add(digest.bytes[i]);
  }
  for(var i=0; i!=4; ++i) {
    hashBytes.add(bdata.getUint8(i));
  }
  for(var i=0; i!=4; ++i) {
    hashBytes.add(0);
  }

  return hex.encode(hashBytes);
}

Future<String> getBigFileHash(File file, int fileSize) async {
  var output = AccumulatorSink<Digest>();
  var input = sha1.startChunkedConversion(output);

  var inStream = file.openRead();
  await for(var chunk in inStream) {
    input.add(chunk);
  }
  input.close();

  var digest = output.events.single;

  final bdata = ByteData(4);
  bdata.setUint32(0, fileSize);

  final hashBytes = List<int>();

  for(var i=0; i!= 20; ++i) {
    hashBytes.add(digest.bytes[i]);
  }
  for(var i=0; i!=4; ++i) {
    hashBytes.add(bdata.getUint8(i));
  }
  for(var i=0; i!=4; ++i) {
    hashBytes.add(0);
  }

  return hex.encode(hashBytes);
}

/*
文件同步线程
通过 port 接收待同步文件 或 指令
上传成功后，回消息

task 流程：
*/
void syncFileTask(SendPort out) {
  ReceivePort port = ReceivePort();
  out.send(port.sendPort);

  print('sync file task started!');
  taskEntry(port, out).then((r) {
    print('sync file task completed, for next.');
  }).catchError((e) {
    print('sync file error! $e');
  });
}

/// [startUploadThread] running on main.dart
Future<void> startUploadThread() async {
  final resp = ReceivePort();
  await Isolate.spawn(syncFileTask, resp.sendPort);

  var first = true;
  var upList = UploadList.i();
  await for(var msg in resp) {
    if(first) {
      var sendPort = msg as SendPort;
      EventBus.initEventBus(sendPort);
      first = false;
      continue;
    }

    // 收到从线程返回的数据
    var result = msg as UploadFileResult;

    // 记录入库
    await DbStorage.i().addSyncFile(SyncFile(
      id: null,
      sessionId: result.sessionId,
      albumId: result.localAlbumId,
      fileHash: result.fileHash,
      sysId: result.localFileId,
      fileId: result.fileId,
    ));

    // 修改 model 标记为已上传 并加入已上传队列
    upList.finishedUploadOne(result);
    if(upList.upStatus != SyncStatus.Running) {
      print('stop!!!');
      continue;
    }

    execNextUpload(upList);
  }
}

/// [execNextUpload] 必须要运行在main线程
void execNextUpload(UploadList upList) {
  // 从队列剩余内容里，找一个重新丢回去
  var head = upList.head;
  if(head != null) {
    EventBus.i().sendPort.send(head);
  } else {
    showToast('已完成上传');
    upList.setSyncFinished();
  }
}
